/*
 * txt2c - Turn a text file into a C++ source file defining a string literal
 * containing that text file's contents. Copyright (C) 2008 Jim Babcock.
 * This program is distributed under the GNU General Public License version 2.
 */
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <stdio.h>

void printUsage(const char *argv0);
void printVersion();

string outputName(string inputName);
string defaultVarName(string inputName);
void writeHeader(FILE *fout, string ns);
void writeTrailer(FILE *fout, string ns);
bool writeBody(FILE *fout, string inputFile, string varName, char commentMarker);



void printUsage(const char *argv0)
{
	printf(
		"Usage: %s [-o <output-file>] <input-file>*\n"
		"   -c <comment-char> Treat the given character as a comment marker. May be the\n"
		"                     char itself, or one of \"hash\" or \"semi\".\n"
		"   -o <output-file>  Write to the given file instead of generating filenames\n"
		"   -n <namespace>    Put the variable(s) defined into <namespace>\n"
		"   -v <varname>      Set the variable's name. (Limit one input file.)\n"
		, argv0);
}

void printVersion()
{
	printf(
	"txt2c v1.0\n"
	"Copyright (C) Jim Babcock. Distributed as a compile-time component of ScreenWM.\n"
	"This is free software; see the source for copying conditions.  There is NO\n"
	"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
	);
}

char parseCommentMarkerOption(string opt)
{
	if(opt=="semi")
		return ';';
	else if(opt=="hash")
		return '#';
	else if(opt.length()==1)
		return opt[0];
	else
	{
		fprintf(stderr, "Unrecognized comment marker: %s.\n", opt.c_str());
		return 0;
	}
}

int main(int argc, char **argv)
{
	bool singleOutput = false;
	string outputFile = "-";
	string ns="";
	string varname="";
	char commentMarker=0;
	vector<string> inputFiles;
	
	for(int ii=1; ii<argc; ii++)
	{
		string arg = argv[ii];
		
		if(arg=="-h" || arg=="--help")
		{
			printUsage(argv[0]);
			return 0;
		}
		else if(arg=="--version" || arg=="-V")
		{
			printVersion();
			return 0;
		}
		else if(arg=="-c" || arg=="--comment")
		{
			ii++;
			if(ii >= argc) {
				fprintf(stderr, "%s: The -c option requires another argument.", argv[0]);
				return 1;
			}
			commentMarker = parseCommentMarkerOption(argv[ii]);
		}
		else if(arg=="-o")
		{
			ii++;
			if(ii >= argc) {
				fprintf(stderr, "%s: The -o option requires another argument.", argv[0]);
				return 1;
			}
			outputFile = argv[ii];
			singleOutput = true;
		}
		else if(arg=="-v" || arg=="--var")
		{
			ii++;
			if(ii >= argc) {
				fprintf(stderr, "%s: The %s option requires another argument.", argv[0], arg.c_str());
				return 1;
			}
			varname = argv[ii];
		}
		else if(arg=="-n" || arg=="--namespace")
		{
			ii++;
			if(ii >= argc) {
				fprintf(stderr, "%s: The %s option requires another argument.", argv[0], arg.c_str());
				return 1;
			}
			ns = argv[ii];
		}
		else
		{
			inputFiles.push_back(arg);
		}
	}
	
	bool success = true;
	
	if(singleOutput)
	{
		FILE *fout = fopen(outputFile.c_str(), "w");
		if(!fout) {
			fprintf(stderr, "Failed opening output file '%s'.\n", outputFile.c_str());
			return 1;
		}
		
		writeHeader(fout, ns);
		for(unsigned ii=0; ii<inputFiles.size(); ii++)
		{
			string vn = varname.length()?varname:defaultVarName(inputFiles[ii]);
			if(writeBody(fout, inputFiles[ii], vn, commentMarker))
				writeTrailer(fout, ns);
			else
				success = false;
		}
		
		fclose(fout);
	}
	else
	{
		for(unsigned ii=0; ii<inputFiles.size(); ii++)
		{
			string input = inputFiles[ii];
			string output = outputName(input);
			FILE *fout = fopen(output.c_str(), "w");
			if(!fout) {
				fprintf(stderr, "Failed opening output file '%s'.\n", output.c_str());
				return 1;
			}
			
			writeHeader(fout, ns);
			string vn = varname.length()?varname:defaultVarName(input);
			if(writeBody(fout, input, vn, commentMarker))
				writeTrailer(fout, ns);
			else
				success = false;
			
			fclose(fout);
		}
	}
	
	if(success)
		return 0;
	else
		return 1;
}

string outputName(string inputName)
{
	return defaultVarName(inputName)+".cpp";
}

string defaultVarName(string inputName)
{
	// Strip the path, extension and any characters not [a-zA-Z0-9]. Then if
	// the first character is [0-9], prepend an _.
	string ret="";
	for(unsigned ii=0; ii<inputName.length(); ii++)
	{
		char ch = inputName[ii];
		if( (ch>='a' && ch<='z') || (ch>='A' && ch<='Z') || (ch>='0' && ch<='9') )
			ret += ch;
		else if(ch=='/' || ch=='\\')
			ret = "";
		else if(ch=='.')
			break;
	}
	if(!ret.length())
		return "_";
	if(ret[0]>='0' && ret[0]<='9')
		return "_"+ret;
	return ret;
}

void writeHeader(FILE *fout, string ns)
{
	fprintf(fout, "/* Generated file - do not edit. */\n");
	if(ns != "")
		fprintf(fout, "namespace %s {\n", ns.c_str());
}

void writeTrailer(FILE *fout, string ns)
{
	if(ns != "")
		fprintf(fout, "}\n");
}

char intToHexDigit(int n)
{
	if(n>=0 && n<=9)
		return n+'0';
	else if(n>=10 && n<=15)
		return n-10+'A';
	else
		return 0;
}

bool writeBody(FILE *fout, string inputFile, string varName, char commentMarker)
{
	FILE *fin = fopen(inputFile.c_str(), "r");
	if(!fin) {
		fprintf(stderr, "Failed opening input file '%s'.\n", inputFile.c_str());
		return false;
	}
	
	fprintf(fout, "const char *%s =\n\"", varName.c_str());
	
	while(!feof(fin))
	{
		int ch = fgetc(fin);
		if(ch==EOF)
			break;
		
		if(ch==commentMarker)
		{
			while(ch!=EOF && ch!='\n')
				ch = fgetc(fin);
			ungetc(ch, fin);
			continue;
		}

		switch(ch)
		{
			case '\n': fprintf(fout, "\\n\"\n\""); break;
			//case '\r': fprintf(fout, "\\r"); break;
			case '\r': break;
			
			case '\\': fprintf(fout, "\\\\"); break;
			case '\"': fprintf(fout, "\\\""); break;
			case '\t': fprintf(fout, "\t"); break;
			
			default:
				if(ch >= 32 & ch < 127) //Printable?
					fputc(ch, fout);
				else
					fprintf(fout, "\\x%c%c",
						intToHexDigit((ch>>4) & 0x0F),
						intToHexDigit((ch   ) & 0x0F));
				break;
		}
	}
	
	fprintf(fout, "\";\n");
	
	fclose(fin);
	return true;
}

